<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>可编辑的贝塞尔曲线</title>
    <style>
        body {
            background: #eeeeee;
        }

        .floatingControls {
            position: absolute;
            left: 150px;
            top: 100px;
            width: 300px;
            padding: 20px;
            border: thin solid rgba(0, 0, 0, 0.3);
            background: rgba(0, 0, 200, 0.1);
            color: blue;
            font: 14px Arial;
            -webkit-box-shadow: rgba(0, 0, 0, 0.2) 6px 6px 8px;
            -moz-box-shadow: rgba(0, 0, 0, 0.2) 6px 6px 8px;
            box-shadow: rgba(0, 0, 0, 0.2) 6px 6px 8px;
            display: none;
        }

        .floatingControls p {
            margin-top: 0px;
            margin-bottom: 20px;
        }


        #controls {
            position: absolute;
            left: 25px;
            top: 25px;
        }

        #canvas {
            background: #ffffff;
            cursor: pointer;
            margin-left: 10px;
            margin-top: 10px;
            -webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
            -moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
            box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
        }
    </style>
</head>
<body>
<canvas id="canvas" width="605" height="400">Canvas not supported</canvas>
<div id='controls'>
    Stroke color: <select id='strokeStyleSelect'>
    <option value='red'>red</option>
    <option value='green'>green</option>
    <option value='blue'>blue</option>
    <option value='orange'>orange</option>
    <option value='cornflowerblue'>cornflowerblue</option>
    <option value='goldenrod'>goldenrod</option>
    <option value='navy' selected>navy</option>
    <option value='purple'>purple</option>
</select>
    Guidewires: <input id='guidewireCheckbox' type='checkbox' checked/>
    <input id='eraseAllButton' type='button' value='Erase all'/>
</div>
<div id='instructions' class='floatingControls'>
    <p>拖动贝塞尔曲线的锚点和控制点以改变曲线的形状</p>

    <p>当你完成曲线形状的编辑后，点击曲线外的一点，以完成此图像</p>

    <input id='instructionsOkayButton' type='button' value='好的' autofocus/>
    <input id='instructionsNoMoreButton' type='button'
           value='不再提示'/>
</div>
</body>
<script>
    'use strict';

    let canvas = document.getElementById('canvas'),
        context = canvas.getContext('2d'),
        eraseAllButton = document.getElementById('eraseAllButton'),
        strokeStyleSelect = document.getElementById('strokeStyleSelect'),
        guidewireCheckbox = document.getElementById('guidewireCheckbox'),
        instructions = document.getElementById('instructions'),
        instructionsOkayButton = document.getElementById('instructionsOkayButton'),
        instructionsNoMoreButton = document.getElementById('instructionsNoMoreButton'),

        showInstructions = true,

        GRID_STROKE_STYLE = 'lightblue',
        GRID_SPACING = 10,

        CONTROL_POINT_RADIUS = 5,
        CONTROL_POINT_STROKE_STYLE = 'blue',
        CONTROL_POINT_FILL_STYLE = 'rgba(255, 255, 0, 0.5)',

        END_POINT_STROKE_STYLE = 'navy',
        END_POINT_FILL_STYLE = 'rgba(0, 255, 0, 0.5)',

        GUIDEWIRE_STROKE_STYLE = 'rgba(0,0,230,0.4)',

        drawingImageData,

        mousedown = {},
        rubberbandRect = {},

        dragging = false,
        draggingPoint = false,

        endPoints = [{}, {}],
        controlPoints = [{}, {}],
        editing = false,

        guidewires = guidewireCheckbox.checked;

    //Function……

    /**
     * 画网格线
     * @param color
     * @param stepX
     * @param stepY
     */
    let drawGrid = (color, stepX, stepY) => {
        context.save()

        context.strokeStyle = color;
        context.lineWidth = 0.5;
        context.clearRect(0, 0, context.canvas.width, context.canvas.height);

        for (var i = stepX + 0.5; i < context.canvas.width; i += stepX) {
            context.beginPath();
            context.moveTo(i, 0);
            context.lineTo(i, context.canvas.height);
            context.stroke();
        }

        for (var i = stepY + 0.5; i < context.canvas.height; i += stepY) {
            context.beginPath();
            context.moveTo(0, i);
            context.lineTo(context.canvas.width, i);
            context.stroke();
        }

        context.restore();
    };

    let updateRubberbandRectangle=(loc)=> {
        rubberbandRect.width  = Math.abs(loc.x - mousedown.x);
        rubberbandRect.height = Math.abs(loc.y - mousedown.y);

        if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x;
        else                     rubberbandRect.left = loc.x;

        if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y;
        else                     rubberbandRect.top = loc.y;
    };

    /**
     * 窗口坐标转化为Canvas坐标
     * @param x
     * @param y
     * @returns {{x: number, y: number}}
     */
    let windowToCanvas = (x, y) => {
        let bbox = canvas.getBoundingClientRect();

        return {
            x: x - bbox.left * (canvas.width / bbox.width),
            y: y - bbox.top * (canvas.height / bbox.height)
        };
    };

    //保存和恢复绘图表面

    let saveDrawingSurface = () => {
        drawingImageData = context.getImageData(0, 0,
            canvas.width, canvas.height);
    };

    let restoreDrawingSurface = () => {
        context.putImageData(drawingImageData, 0, 0);
    };

    /**
     * 绘制贝塞尔曲线
     */
    let drawBezierCurve = () => {
        context.beginPath();
        context.moveTo(endPoints[0].x, endPoints[0].y);
        context.bezierCurveTo(controlPoints[0].x, controlPoints[0].y,
            controlPoints[1].x, controlPoints[1].y,
            endPoints[1].x, endPoints[1].y);
        context.stroke();
    };

    let updateEndAndControlPoints=()=> {
        endPoints[0].x = rubberbandRect.left;
        endPoints[0].y = rubberbandRect.top;

        endPoints[1].x = rubberbandRect.left + rubberbandRect.width;
        endPoints[1].y = rubberbandRect.top  + rubberbandRect.height

        controlPoints[0].x = rubberbandRect.left;
        controlPoints[0].y = rubberbandRect.top  + rubberbandRect.height

        controlPoints[1].x = rubberbandRect.left + rubberbandRect.width;
        controlPoints[1].y = rubberbandRect.top;
    };

    let drawRubberbandShape=(loc)=> {
        updateEndAndControlPoints();
        drawBezierCurve();
    };

    let updateRubberband=(loc)=> {
        updateRubberbandRectangle(loc);
        drawRubberbandShape(loc);
    };

    //辅助线

    let drawHorizontalGuidewire = (y) => {
        context.beginPath();
        context.moveTo(0, y + 0.5);
        context.lineTo(context.canvas.width, y + 0.5);
        context.stroke();
    };

    let drawVerticalGuidewire = (x) => {
        context.beginPath();
        context.moveTo(x + 0.5, 0);
        context.lineTo(x + 0.5, context.canvas.height);
        context.stroke();
    };

    let drawGuidewires = (x, y) => {
        context.save();
        context.strokeStyle = GUIDEWIRE_STROKE_STYLE;
        context.lineWidth = 0.5;
        drawVerticalGuidewire(x);
        drawHorizontalGuidewire(y);
        context.restore();
    };

    //绘制锚点和控制点

    let drawControlPoint = (index) => {
        context.beginPath();
        context.arc(controlPoints[index].x, controlPoints[index].y,
            CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);
        context.stroke();
        context.fill();
    };

    let drawControlPoints = () => {
        context.save();
        context.strokeStyle = CONTROL_POINT_STROKE_STYLE;
        context.fillStyle = CONTROL_POINT_FILL_STYLE;

        drawControlPoint(0);
        drawControlPoint(1);

        context.stroke();
        context.fill();
        context.restore();
    };

    let drawEndPoint = (index) => {
        context.beginPath();
        context.arc(endPoints[index].x, endPoints[index].y,
            CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);
        context.stroke();
        context.fill();
    };

    let drawEndPoints = () => {
        context.save();
        context.strokeStyle = END_POINT_STROKE_STYLE;
        context.fillStyle = END_POINT_FILL_STYLE;

        drawEndPoint(0);
        drawEndPoint(1);

        context.stroke();
        context.fill();
        context.restore();
    };

    let drawControlAndEndPoints = () => {
        drawControlPoints();
        drawEndPoints();
    };

    let cursorInEndPoint = (loc) => {
        var pt;

        endPoints.forEach(function (point) {
            context.beginPath();
            context.arc(point.x, point.y,
                CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);

            if (context.isPointInPath(loc.x, loc.y)) {
                pt = point;
            }
        });

        return pt;
    };

    let cursorInControlPoint = (loc) => {
        var pt;

        controlPoints.forEach(function (point) {
            context.beginPath();
            context.arc(point.x, point.y,
                CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);

            if (context.isPointInPath(loc.x, loc.y)) {
                pt = point;
            }
        });

        return pt;
    };

    let updateDraggingPoint = (loc) => {
        draggingPoint.x = loc.x;
        draggingPoint.y = loc.y;
    };

    //事件


    canvas.onmousedown = (e) => {
        //获取当前位置
        let loc = windowToCanvas(e.clientX,e.clientY);
        e.preventDefault();

        if (!editing){

            saveDrawingSurface();
            mousedown.x =loc.x;
            mousedown.y = loc.y;
            updateRubberbandRectangle(loc);
            dragging=true;
        }
        else{
            draggingPoint = cursorInControlPoint(loc);
            if (!draggingPoint){

                draggingPoint=cursorInEndPoint(loc);
            }
        }
    };
    canvas.onmousemove=e=>{

        let loc = windowToCanvas(e.clientX,e.clientY);
        if (dragging||draggingPoint){
            e.preventDefault();
            restoreDrawingSurface();
            if (guidewires){
                drawGuidewires(loc.x,loc.y);
            }
        }
        if (dragging) {
            updateRubberband(loc);
            drawControlAndEndPoints();
        }
        else if (draggingPoint) {
            updateDraggingPoint(loc);
            drawControlAndEndPoints();
            drawBezierCurve();
        }
    };

    canvas.onmouseup=e=>{
        let loc = windowToCanvas(e.clientX,e.clientY);

        restoreDrawingSurface();

        if (!editing) {
            updateRubberband(loc);
            drawControlAndEndPoints();
            dragging = false;
            editing = true;
            if (showInstructions) {
                instructions.style.display = 'inline';
            }
        }
        else {
            if (draggingPoint) drawControlAndEndPoints();
            else editing = false;

            drawBezierCurve();
            draggingPoint = undefined;
        }
    }

    // 给控件添加监听

    eraseAllButton.onclick = ()=> {
        context.clearRect(0, 0, canvas.width, canvas.height);
        drawGrid(GRID_STROKE_STYLE, GRID_SPACING, GRID_SPACING);

        saveDrawingSurface();

        editing = false;
        dragging = false;
        draggingPoint = undefined;
    };

    strokeStyleSelect.onchange = ()=>{
        context.strokeStyle = strokeStyleSelect.value;
    };

    guidewireCheckbox.onchange = ()=> {
        guidewires = guidewireCheckbox.checked;
    };

    // 给介绍框添加监听

    instructionsOkayButton.onclick = ()=> {
        instructions.style.display = 'none';
    };

    instructionsNoMoreButton.onclick = ()=> {
        instructions.style.display = 'none';
        showInstructions = false;
    };

    // 初始化

    context.strokeStyle = strokeStyleSelect.value;
    drawGrid(GRID_STROKE_STYLE, GRID_SPACING, GRID_SPACING);

</script>
</html>